#include "basic.h"
#include "io.h"
#include "ping.h"
#include <pthread.h>
#include "List/simclist_impl.h"
#include "host_management.h"

#include "file_management.h"
#include "query_management.h"
#include "connections.h"
#include "boot_methods.h"

#include <math.h>

void superpeer_routine(in_addr_t IP) {

	fd_set rset; //insieme dei descrittori legati alle socket sulle quali lavora la select

	int listensd, connsd; // descrittori in lettura e socket di connessione
	int maxd = 0; //valore massimo tra i descrittori nella select
	int ready; // numero di descrittori pronti dopo la select

	int listen_to_update; // descrittore socket tcp per update file
	int join_leave_socket;
	int ping_socket, query_serve_peer_socket, query_help_socket,
			rcv_list_socket, check_file_socket;

	struct in_addr myIP;
	myIP.s_addr = IP;


	struct sockaddr_in fileaddr, updateaddr, connected_addr,
			join_leave_address, ping_address, query_help_addr,
			query_serve_addr, rcv_list_addr, check_file_address;
	char inputline[1024]; // lettura comandi da input

	list_t peer_list; // lista dei peer associati al superpeer
	list_init(&peer_list);
	list_attributes_comparator(&peer_list, list_comparator_uint32_t);
	list_attributes_seeker(&peer_list, ip_file_seeker);
	list_attributes_copy(&peer_list, list_meter_uint32_t, 1);

	list_t file_list; // lista file-->ip peer associati
	list_init(&file_list);
	list_attributes_comparator(&file_list, string_file_comparator);
	list_attributes_seeker(&file_list, string_file_seeker);
	list_attributes_copy(&file_list, list_file_meter, 1);

	list_t my_file_list; // lista dei miei file
	list_init(&my_file_list);
	list_attributes_comparator(&my_file_list, list_comparator_string);
	list_attributes_seeker(&my_file_list, string_seeker);
	list_attributes_copy(&my_file_list, list_meter_string, 1);


	printf("RICHIEDI FILE:            get_file \n"); // al peer generico
	printf("RICHIEDI LISTA SUPERPEER: get_superpeer_list \n"); // al bootstrap (torna la lista dei superpeer)
	printf("LASCIA LA RETE            leave \n"); // al bootstrap

	/*  SOCKET PER SPEDIRE FILE */
	if ((listensd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		printf("errore in socket su listensd riavviare superpeer\n");
		exit(1);
	}

	memset((void *) &fileaddr, 0, sizeof(fileaddr));
	fileaddr.sin_family = AF_INET;
	fileaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	fileaddr.sin_port = htons(FILE_PORT);

	if ((bind(listensd, (struct sockaddr *) &fileaddr, sizeof(fileaddr))) < 0) {
		printf("errore in bind su listensd riavviare il superpeer\n");
		exit(1);
	}

	if (listen(listensd, BACKLOG) < 0) { // numero massimo di connessioni che possono essere accettate
		printf("errore in listen su listensd riavviare il superpeer\n");
		exit(1);
	}
	// ---

	/* SOCKET PER RICEVERE AGGIORNAMENTI LISTA */
	if ((listen_to_update = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		printf("errore in socket su listen_to_update riavviare il superpeer\n");
		exit(1);
	}

	memset((void *) &updateaddr, 0, sizeof(updateaddr));
	updateaddr.sin_family = AF_INET;
	updateaddr.sin_addr.s_addr = htonl(INADDR_ANY); // in ascolto per qualunque indirizzo alla porta SERV_PORT2
	updateaddr.sin_port = htons(UPDATE_FILE_PORT); // sostituire con FILE_PORT

	if ((bind(listen_to_update, (struct sockaddr *) &updateaddr,
			sizeof(updateaddr))) < 0) {
		printf("errore in bind in listen_to_update riavviare il superpeer\n");
		exit(1);
	}

	if (listen(listen_to_update, BACKLOG) < 0) { // numero massimo di connessioni che possono essere accettate
		printf("errore in listen listen_to_update riavviare il superpeer\n");
		exit(1);
	}
	// ---

	/* SOCKET DI JOIN E LEAVE */
	if ((join_leave_socket = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		printf(
				"errore nella creazione del socket join_leave_socket riavviare il superpeer\n");
		exit(1);
	}

	memset((void*) &join_leave_address, 0, sizeof(join_leave_address));
	join_leave_address.sin_family = AF_INET;
	join_leave_address.sin_port = htons(JOIN_LEAVE_PORT);
	join_leave_address.sin_addr.s_addr = htonl(INADDR_ANY);

	if (bind(join_leave_socket, (struct sockaddr*) &join_leave_address,
			sizeof(join_leave_address)) < 0) {
		printf("errore nella bind join_leave_socket riavviare il superpeer\n");
		exit(1);

	}
	// ---

	/* SOCKET DI PING*/
	if ((ping_socket = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		printf(
				"errore nella creazione del socket ping_socket riavviare il superpeer\n");
		exit(1);
	}

	memset((void*) &ping_address, 0, sizeof(ping_address));
	ping_address.sin_family = AF_INET;
	ping_address.sin_port = htons(PING_PORT);
	ping_address.sin_addr.s_addr = htonl(INADDR_ANY);

	if (bind(ping_socket, (struct sockaddr*) &ping_address,
			sizeof(ping_address)) < 0) {
		printf("errore nella bind su ping_socket riavviare il superpeer\n");
		exit(1);

	}
	// ---

	/* SOCKET SERVE PEER */
	if ((query_serve_peer_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		printf(
				"Socket creation error su query_serve_peer_socket riavviare il superpeer\n");
		exit(1);
	}

	memset((void *) &query_serve_addr, 0, sizeof(query_serve_addr));
	query_serve_addr.sin_family = AF_INET;
	query_serve_addr.sin_port = htons(QUERY_SERVE_PEER_PORT);
	query_serve_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	if (bind(query_serve_peer_socket, (struct sockaddr *) &query_serve_addr,
			sizeof(query_serve_addr)) < 0) {
		printf("bind error on query_serve_peer_socket riavviare il superpeer\n");
		exit(1);
	}

	if (listen(query_serve_peer_socket, BACKLOG) < 0) { // numero massimo di connessioni che possono essere accettate
		printf(
				"errore in listen su query_serve_peer_socket riavviare il superpeer\n");
		exit(1);
	}
	// ---

	/*  SOCKET QUERY HELP */
	if ((query_help_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		printf(
				"Socket creation error on query_help_socket riavviare il superpeer\n");
		exit(1);
	}

	memset((void *) &query_help_addr, 0, sizeof(query_help_addr));
	query_help_addr.sin_family = AF_INET;
	query_help_addr.sin_port = htons(QUERY_HELP_PORT);
	query_help_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	if (bind(query_help_socket, (struct sockaddr *) &query_help_addr,
			sizeof(query_help_addr)) < 0) {
		printf("bind error on query_help_socket riavviare il superpeer\n");
		exit(1);
	}

	if (listen(query_help_socket, BACKLOG) < 0) { // numero massimo di connessioni che possono essere accettate
		printf("errore in listen su query_help_socket riavviare il superpeer\n");
		exit(1);
	}
	// ---

	/* SOCKET RCV LIST SOCKET */
	if ((rcv_list_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		printf(
				"Socket creation error su rcv_list_socket riavviare il superpeer\n");
		exit(1);
	}

	memset((void *) &rcv_list_addr, 0, sizeof(rcv_list_addr));
	rcv_list_addr.sin_family = AF_INET;
	rcv_list_addr.sin_port = htons(RCV_LIST_PORT);
	rcv_list_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	if (bind(rcv_list_socket, (struct sockaddr *) &rcv_list_addr,
			sizeof(rcv_list_addr)) < 0) {
		printf("bind error on rcv_list_socket riavviare il superpeer\n");
		exit(1);
	}

	if (listen(rcv_list_socket, BACKLOG) < 0) { // numero massimo di connessioni che possono essere accettate
		printf("errore in listen rcv_list_socket riavviare il superpeer\n");
		exit(1);
	}
	//---

	/* CHECK FILE SOCKET */

	if ((check_file_socket = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		printf(
				"errore nella creazione del socket check_file_socket riavviare il superpeer\n");
		exit(1);
	}

	memset((void*) &check_file_address, 0, sizeof(check_file_address));
	check_file_address.sin_family = AF_INET;
	check_file_address.sin_port = htons(CHECK_FILE_PORT);
	check_file_address.sin_addr.s_addr = htonl(INADDR_ANY);

	if (bind(check_file_socket, (struct sockaddr*) &check_file_address,
			sizeof(check_file_address)) < 0) {
		printf("errore nella bind check_file_socket riavviare il superpeer\n");
		exit(1);
	}
	// ---

	pthread_t t_update;
	update_arg_t u_arguments;
	u_arguments.is_superpeer = 1; // è superpeer
	u_arguments.list = &my_file_list; // passo la lista dei miei file (il thread me la aggiorna)

	pthread_create(&t_update, NULL, (void*) &update_file, &u_arguments); // thread che controlla gli aggiornamenti dei file condivisi

	pthread_t t_serve_join_leave; // join e leave
	host_mgt_struct_t jl_arguments;
	jl_arguments.peer_list = &peer_list;
	jl_arguments.file_list = &file_list;
	jl_arguments.socket = join_leave_socket;

	pthread_create(&t_serve_join_leave, NULL, (void*) &joinleave_routine,
			&jl_arguments);

	pthread_t t_ping_boot; // riceve-risponde ping bootstrap
	host_mgt_struct_t ping_arguments;
	ping_arguments.socket = ping_socket;
	ping_arguments.is_superpeer = 1;

	pthread_create(&t_ping_boot, NULL, (void*) &answer_ping, &ping_arguments);

	pthread_t _t_ping_peer; // effettua-riceve ping ai peer
	host_mgt_struct_t peer_ping_arguments;
	peer_ping_arguments.peer_list = &peer_list;
	peer_ping_arguments.file_list = &file_list;
	pthread_create(&_t_ping_peer, NULL, (void*) ping_peer_routine,
			&peer_ping_arguments);

	// CHECK FILE THREAD
	pthread_t t_check_file;
	host_mgt_struct_t check_struct_arg;
	check_struct_arg.file_list = &file_list;
	check_struct_arg.my_file_list = &my_file_list;
	check_struct_arg.socket = check_file_socket;
	pthread_create(&t_check_file, NULL, (void*) &find_file_routine,
			&check_struct_arg);

	// "FAKE LIST" SERVE NEL QUERY FLOODING NEL CASO IL SUPERPEER HA IL FILE RICHIESTO DAL PEER ASSOCIATO
	list_t fake_list;
	list_init(&fake_list);
	list_attributes_comparator(&fake_list, list_comparator_uint32_t);
	list_attributes_seeker(&fake_list, ip_file_seeker);
	list_attributes_copy(&fake_list, list_meter_uint32_t, 1);
	list_attributes_serializer(&fake_list, element_serializer_peer_list);

	FD_ZERO(&rset);
	/* Inizializza a zero l'insieme dei descrittori */

	FD_SET(listensd, &rset);
	if (listensd > maxd)
		maxd = listensd;
	/* Inserisce il descrittore di ascolto */
	FD_SET(listen_to_update, &rset);
	if (listen_to_update > maxd)
		maxd = listen_to_update;

	FD_SET(query_serve_peer_socket, &rset);
	if (query_serve_peer_socket > maxd)
		maxd = query_serve_peer_socket;

	FD_SET(rcv_list_socket, &rset);
	if (rcv_list_socket > maxd)
		maxd = rcv_list_socket;

	FD_SET(query_help_socket, &rset);
	if (query_help_socket > maxd)
		maxd = query_help_socket;

	FD_SET(fileno(stdin), &rset);
	if (fileno(stdin) > maxd)
		maxd = fileno(stdin);

	fd_set mem_set;
	FD_ZERO(&mem_set);

	mem_set = rset;

	while (1) {

		FD_ZERO(&rset);
		rset = mem_set;

		struct timeval timeout_select;
		timeout_select.tv_sec = 2;
		timeout_select.tv_usec = 0;

		int k = 0;
		if ((ready = select(FD_SETSIZE, &rset, NULL, NULL, &timeout_select))
				< 0) {
			printf("errore in select\n");
			continue;
		}

		if (ready == 0) {
			continue;
		}


		if (FD_ISSET(listensd, &rset)) {
			printf("Richiesto file\n");
			fflush(stdout);

			socklen_t len = sizeof(connected_addr);
			if ((connsd = accept(listensd, (struct sockaddr *) &connected_addr,
					&len)) < 0) {
				perror("errore in accept listensd"); // se backlog vuoto la select si blocca e attende connessioni
				continue;
			}

			file_arg_t arguments;
			arguments.socket = connsd;

			pthread_t t1;
			pthread_create(&t1, NULL, (void*) &send_file, (void*) &arguments);

			printf("RICHIESTO FILE DA \n", inet_ntoa(connected_addr.sin_addr));

		}

		if (FD_ISSET(listen_to_update, &rset)) {


			socklen_t len = sizeof(connected_addr);
			if ((connsd = accept(listen_to_update,
					(struct sockaddr *) &connected_addr, &len)) < 0) {
				printf("errore in accept listen_to_update\n"); // se backlog vuoto la select si blocca e attende connessioni
				continue;
			}

			pthread_t t_update;
			update_arg_t arguments;

			arguments.list = &file_list;
			arguments.ip = connected_addr.sin_addr.s_addr;
			arguments.socket = connsd;

			pthread_create(&t_update, NULL, (void*) &update_list,
					(void*) &arguments);

		}

		if (FD_ISSET(query_help_socket, &rset)) {

			socklen_t len = sizeof(connected_addr);
			if ((connsd = accept(query_help_socket,
					(struct sockaddr *) &connected_addr, &len)) < 0) {
				printf("errore in accept listen_to_update\n"); // se backlog vuoto la select si blocca e attende connessioni
				continue;
			}

			pthread_t t_query_help;
			check_sublist_struct_t arg;
			arg.upperSocket = connsd; // socket alla quale devo restituire la lista
			arg.my_file_list = &my_file_list; // controllo prima qui se ho il file richiesto, poi check su sublist ricevuta
			arg.file_list = &file_list;
			arg.superpeer_ip = myIP.s_addr;

			pthread_create(&t_query_help, NULL, (void*) &serve_query_request,
					(void*) &arg);

		}

		if (FD_ISSET(query_serve_peer_socket, &rset)) {

			socklen_t len = sizeof(connected_addr);
			if ((connsd = accept(query_serve_peer_socket,
					(struct sockaddr *) &connected_addr, &len)) < 0) {
				printf("errore in accept query_serve_peer_socket"); // se backlog vuoto la select si blocca e attende connessioni
				continue;
			}

			check_sublist_struct_t arg;
			arg.peerSocket = connsd;
			arg.superpeer_ip = myIP.s_addr;
			arg.my_file_list = &my_file_list;
			arg.file_list = &file_list;

			pthread_t query_serve_peer_s;
			pthread_create(&query_serve_peer_s, NULL,
					(void*) &query_serve_peer_socket_routine, (void*) &arg);

		}

		if (FD_ISSET(rcv_list_socket, &rset)) {

			printf("RICHIESTA LISTA PEER\n");
			socklen_t len = sizeof(connected_addr);
			if ((connsd = accept(rcv_list_socket,
					(struct sockaddr *) &connected_addr, &len)) < 0) {
				printf("errore in accept rcv_list_socket");
				continue;
			}

			check_sublist_struct_t arg;
			arg.peerSocket = connsd;
			arg.superpeer_ip = myIP.s_addr;
			arg.my_file_list = &my_file_list;
			arg.peer_file_list = &file_list;

			pthread_t rcv_list_s;
			pthread_create(&rcv_list_s, NULL, (void*) &rcv_list_socket_routine,
					(void*) &arg);

		}

		if (FD_ISSET(fileno(stdin), &rset)) { // Se può leggere dallo stdin

			if ((fgets(inputline, 1024, stdin) < 0)) {
				printf("Errore in fgets\n");
				continue;
			}

			if (inputline == NULL) {
				continue;
			}

			if ((strncmp(inputline, "get_file", 8) == 0) && (inputline[9]
					== '\0')) {

				FD_CLR(fileno(stdin),&mem_set);

				check_sublist_struct_t arguments;
				arguments.file_list = &file_list;
				arguments.lowerSocket = rcv_list_socket;
				arguments.upperSocket = query_help_socket;
				arguments.peerSocket = NULL;
				arguments.superpeer_ip = myIP.s_addr;
				arguments.mem_set = &mem_set;

				pthread_t t_get_file;
				pthread_create(&t_get_file, NULL, (void*) &get_file_superpeer,
						&arguments);
			}

			else if ((strncmp(inputline, "get_superpeer_list", 18) == 0)
					&& (inputline[19] == '\0')) {
				pthread_t t1;
				pthread_create(&t1, NULL, (void*) &get_superpeer_list_thread,
						NULL);

			}

			else if ((strncmp(inputline, "leave", 5) == 0) && (inputline[6]
					== '\0')) { // lascio la rete
				pthread_kill(t_update);
				pthread_kill(joinleave_routine);
				pthread_kill(answer_ping);
				pthread_kill(ping_peer_routine);
				pthread_kill(find_file_routine);
				close(listensd);
				close(listen_to_update);
				close(join_leave_socket);
				close(ping_socket);
				close(query_serve_peer_socket);
				close(query_help_socket);
				close(rcv_list_socket);
				close(check_file_socket);
				printf("--------Chiuso Programma--------\n");
				exit(0);
			}

		}

	}
}
